#include <text.h>
#include <std.h>
#include <pio.h>
#include <error.h>
#include <dint.h>
#include <keyboard.h>


#define PALETTE_COLOR_NUMBER 0x3c8
#define PALETTE_RED          0x3c9
#define PALETTE_GREEN        0x3c9
#define PALETTE_BLUE         0x3c9

#define INDENT 24

unsigned char NORM_TEXT=7;
unsigned char ALERT_TEXT=11;

#define VSCREEN_END_SCROLL 1920

static unsigned char textcolor=7;
static unsigned short screen_end=VSCREEN_END_SCROLL;
static unsigned char *vgafb=(unsigned char *)0xb8000;
static unsigned short crtcio=0x3d4;


#define BUF_SZ 32768
#define TEXT_BUFFER_WRITE 0
#define TEXT_BUFFER_NOWRITE 1
#define ADD_ALLOC 32768
static struct buffer_t	{
	char *buffer;
	unsigned long buf_sz;
	unsigned long buffer_p;
	unsigned long start_p;
	spin_lock_t buffer_lock;
	int flags;
	unsigned long screen_size;
	unsigned long line_size;
}text={.buffer=NULL,.buf_sz=BUF_SZ,.buffer_p=0,.buffer_lock=SPINRELEASE,.flags=TEXT_BUFFER_WRITE,.screen_size=3840,.line_size=160};


void add_line()
{
	char *new_buf,*old_buf;
	if(!text.buffer_p) {	/*Copy the entire screen at the start..and then do it line by line as we scroll*/
		memcpy(text.buffer,vgafb,text.screen_size+text.line_size);
		text.buffer_p+=text.screen_size+text.line_size;
		return;
	}
	memcpy(text.buffer+text.buffer_p,vgafb+text.screen_size,text.line_size);
	text.buffer_p+=text.line_size;
	text.start_p+=text.line_size;
	if(text.buffer_p>(text.buf_sz-text.line_size))	{	/*Reallocate the buffer if it is going to overflow..*/
		text.flags=TEXT_BUFFER_NOWRITE;		
		new_buf=(char *)get_mem(text.buf_sz+ADD_ALLOC);
		text.buf_sz+=ADD_ALLOC;
		memcpy(new_buf,text.buffer,text.buffer_p);
		old_buf=text.buffer;
		text.buffer=new_buf;
		free(old_buf);
		printk("\nReallocated text buffer");
		text.flags=TEXT_BUFFER_WRITE;
	}
}

void display_buf(int key)
{
	if(key==1)
		text.start_p-=text.line_size;
	else if(key==0)
		text.start_p=text.buffer_p-text.screen_size;
	else
		text.start_p+=text.line_size;
	memcpy(vgafb,text.buffer+text.start_p,text.screen_size);
}


/*This function is called after the memory management is started*/
int text_buf_init()
{
	text.buffer=(char *)get_mem(BUF_SZ*sizeof(char));
	text.buffer_p=NULL;
	text.start_p=NULL;
}

static unsigned short cursor=0;


/*File contains text mode management code... This file displays no error messages*/

void text_normal()
{
	textcolor=NORM_TEXT;
}

void text_alert()
{
	textcolor=ALERT_TEXT;
}

void set_norm_color(unsigned char c)
{
	NORM_TEXT=c;
	text_normal();
}

void set_alert_color(unsigned char c)
{
	ALERT_TEXT=c;
	text_alert();
}

void putstring(char *s)
{
	while(*s!=0)	{
		putchar_text(*s++);
	}
}

/*Detect video address,screen size and put it in the text struct*/
int init_video()
{
	if((inb(VGA_MISC_READ) & 0x01) != 0)
	{
		vgafb = (unsigned char *)0xB8000L;
		crtcio = 0x3D4;
	}
	else
	{
		vgafb = (unsigned char *)0xB0000L;
		crtcio = 0x3B4;
	}
	screen_end= (*(unsigned short *)0x44A)* (*(unsigned char *)0x484 + 1);
	//setpalette(0,10,10,10);
	//setpalette(7,0,0,0);
	outb(crtcio, 15);
	cursor|=inb(crtcio+1);
 	outb(crtcio, 14);
 	cursor|=(inb(crtcio+1)<<8);
	return SUCCESS;
}


/*Im not using the hardware cursor.. I have a software cursor*/
unsigned short getcursor(void)
{
	return cursor;
}

void setcursor(unsigned short offset)
{
	outb(crtcio, 15);
	outb(crtcio+1,(u8)offset);
 	outb(crtcio, 14);
 	outb(crtcio+1,(u8)(offset>>8));
	cursor=offset;
}

void setswcursor(unsigned short offset)
{
	cursor=offset;
}

int clrscr()
{
  unsigned char *vidmem = (unsigned char *)vgafb;
  const long size = 80*25;
  long loop;
  for (loop=0; loop<size; loop++) {
    *vidmem++ = 0;
    *vidmem++ = textcolor;
  }
  setcursor(0);
  return SUCCESS;
}

int fclrscr()
{
  unsigned char *vidmem = (unsigned char *)vgafb;
  const long size = 80*25;
  long loop;
  int i=0;
  for (loop=0; loop<size; loop++) {
    *vidmem++ = 0;
    *vidmem++ = textcolor;
  }
  setcursor(0);
  return SUCCESS;
}

/*Shift the bytes such that the screen gets scrolled...Seems to be a slow
function. Doesn't matter really*/
int slow_scroll_screen(void)
{
	char ch,col;
	unsigned short offset=0;
	unsigned char *vidmem=(unsigned char *)vgafb;
	while(offset<(screen_end))	{
		ch=*(vidmem+offset*2);
		col=*(vidmem+offset*2+1);
		*(vidmem+offset*2-160)=ch;
		*(vidmem+offset*2-160+1)=col;
		offset++;
	}
	while(offset<(screen_end+160))	{
		*(vidmem+offset*2-160)=0;
		*(vidmem+offset*2-160+1)=7;
		offset++;
	}
	setcursor(getcursor()-80);
	return SUCCESS;
}

/*Faster scrolling..*/
int scroll_screen(void)
{
	int i;
	unsigned int *vidmem=(unsigned int *)vgafb;
	if(likely(text.buffer && text.flags==TEXT_BUFFER_WRITE))
		add_line();
	for(i=0;i<80*24;i+=8)	{
		*(vidmem+i)=*(vidmem+i+40);
		*(vidmem+i+1)=*(vidmem+i+1+40);
		*(vidmem+i+2)=*(vidmem+i+2+40);
		*(vidmem+i+3)=*(vidmem+i+3+40);
		*(vidmem+i+4)=*(vidmem+i+4+40);
		*(vidmem+i+5)=*(vidmem+i+5+40);
		*(vidmem+i+6)=*(vidmem+i+6+40);
		*(vidmem+i+7)=*(vidmem+i+7+40);
	}
	setcursor(getcursor()-80);
	return SUCCESS;
}

/*Code used by '\n' */
void skipline(int nol)
{
	unsigned short offset;
	offset=getcursor();
	offset=(offset/80) * 80 + 80 * nol;
	setcursor(offset);
}

/*tab(1,8)
offset=88
y=80
x=8
res=8
*/
/*Code used by '\t' */
void tab(char count,unsigned char tabcount)
{
	unsigned int offset,x,y,res;
	offset=getcursor();
	y=80*(offset/80);
	x=offset-y;
	res=tabcount;
	while(x>=res)	{
		res+=tabcount;
	}
	y+=res;
	count--;
	while(--count>0)	{
		y+=tabcount;
	}
	setcursor(y);
}

/*Change the text color.*/
void settextcolor(unsigned char color)
{
	textcolor=color;
}

/*This is a stupid adjustment... The hstd.c printf functions calls this putchar below
and to change it to the graphics one we use set_put_char..*/
int (*putch_fn)(int c);
void set_put_char(void *fn)
{
	putch_fn=fn;
}

void write_vga(unsigned short offset,unsigned char ch)
{
	unsigned char *vidmem=(unsigned char *)vgafb;
	vidmem += offset*2;
	*vidmem=(unsigned char)ch;
	*(vidmem+1)=textcolor;
}

/*Put char in teletype mode*/
int putchar_text(int ch)
{
	unsigned short offset;
	if(putch_fn!=NULL)	{
		putch_fn(ch);
		return NULL;
	}
	offset=getcursor();
	if(ch=='\n')	{
		skipline(1);
	} else if(ch=='\t')	{
		
	} else	{
		write_vga(offset,ch);
		offset++;
		cursor=offset;
	}
	offset=getcursor();
	setcursor(offset);
	if(offset>=screen_end)	{
		scroll_screen();
	}
	return SUCCESS;
}

void update_cursor()
{
	setcursor(cursor);
}

/*Teletype with color*/
int putc(unsigned char ch,unsigned char color)
{
	unsigned short offset;
	unsigned char *vidmem=(unsigned char *)vgafb;
	offset=getcursor();
	vidmem += offset*2;
 	*vidmem=(unsigned char)ch;
	*(vidmem+1)=color;
 	offset++;
 	setcursor(offset);
	return SUCCESS;
}

int setpalette(unsigned char color,unsigned char r,unsigned char g,unsigned char b)
{
	outb(PALETTE_COLOR_NUMBER,color);
	outb(PALETTE_RED,r);
	outb(PALETTE_GREEN,g);
	outb(PALETTE_BLUE,b);
	return SUCCESS;
}

void movecursor(char amt)
{
	unsigned short offset;
	offset=getcursor();
	offset+=amt;
	setcursor(offset);
}

/*Text mode menu driven interface
Call init_opt passing number of options
Add all options using add_opt
Get user's selection using getopt 
*/

static unsigned short glob_menuc=0;
static unsigned short glob_menustart=0;

void init_opt(char noopt)
{
	unsigned short c;
	c=(getcursor()/80)+1;
	glob_menuc=glob_menustart=c;		
}

void addopt(char noopt,char *opts)
{
	init_opt(noopt);
	setcursor(glob_menuc*80);
	printk(opts);
	glob_menuc+=noopt;
}

#define HL_COLOR 0xf0
#define TXT_COLOR 7

void lum(unsigned short optno)
{
	int i=0;
	unsigned char *vid=(unsigned char *)0xb8000;
	optno*=80;
	vid+=optno*2+1;
	if(*vid<=15)
	{
		while(*(vid+i-1)!=0 || i<50)
		{
			*(vid+i)=HL_COLOR;
			i+=2;		
		}
	}
	else
	{
		while(*(vid+i-1)!=0 || i<50)
		{
			*(vid+i)=TXT_COLOR;
			i+=2;		
		}
	}
}

unsigned int getopt()
{
	int c;
	char opt;
	opt=glob_menustart;
	lum(opt);
	while(1)
	{
		c=getch();
		switch(c)
		{
			case 0x0a:setcursor(glob_menuc*80+80);
			return opt-glob_menustart;
			break;
			case 0x97:if(opt>glob_menustart)
			{
				lum(opt);
				opt--;
				lum(opt);
			}
			else
			{
				lum(opt);
				opt=glob_menuc-1;
				lum(opt);
			}
			break;
			case 0x98:if(opt<(glob_menuc-1))
			{
				lum(opt);
				opt++;
				lum(opt);
			}
			else
			{
				lum(opt);
				opt=glob_menustart;
				lum(opt);
			}
			break;
			case 0x96: return 0xf000;
		}
	}
	printk("\n\n");
}

